package com.asmtest.visitor;

import org.junit.Assert;
import org.junit.Test;
import org.objectweb.asm.signature.SignatureReader;

import java.util.Collections;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2023/12/29 15:53
 */
public class GenericTypeExtractorTest {

    @Test
    public void testTypeVariable() {
        // E xxx(E arg0)
        String signature = "<E:Ljava/lang/Object;>(TE;)TE;";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList(""), extractor.getParamGenerics());
    }

    @Test
    public void testGenericWithArray() {
        // List<int[]> xxx(List<Integer[]> arg0)
        String signature = "(Ljava/util/List<[Ljava/lang/Integer;>;)Ljava/util/List<[I>;";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("<[I>", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<[Ljava.lang.Integer>"), extractor.getParamGenerics());
    }

    @Test
    public void testNestedTypeVariable() {
        // <T> void xxx(List<Set<T> arg0)
        String signature = "<T:Ljava/lang/Object;>(Ljava/util/List<Ljava/util/Set<TT;>;>;)V";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<java.util.Set<T>>"), extractor.getParamGenerics());
    }

    @Test
    public void testMultipleTypeVariable() {
        // <E, K, V> E xxx(Map<K, V> arg0)
        String signature = "<E:Ljava/lang/Object;K:Ljava/lang/Object;V:Ljava/lang/Object;>(Ljava/util/Map<TK;TV;>;)TE;";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<K,V>"), extractor.getParamGenerics());
    }

    @Test
    public void testArrayWithGenerics() {
        // List<String[]> xxx(List<String> arg0)
        String signature = "(Ljava/util/List<Ljava/lang/String;>;)Ljava/util/List<[Ljava/lang/String;>;";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("<[Ljava.lang.String>", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<java.lang.String>"), extractor.getParamGenerics());
    }

    @Test
    public void testNestedWildcardGenerics() {
        // void xxx(List<? extends List<? super Number>> arg0)
        String signature = "(Ljava/util/List<+Ljava/util/List<-Ljava/lang/Number;>;>;)V";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<? extends java.util.List<? super java.lang.Number>>"), extractor.getParamGenerics());
    }

    @Test
    public void testComplexNestedGenerics() {
        // void xxx(Map<String, List<Set<Integer[]>>> arg0)
        String signature = "(Ljava/util/Map<Ljava/lang/String;Ljava/util/List<Ljava/util/Set<[Ljava/lang/Integer;>;>;>;)V";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList("<java.lang.String,java.util.List<java.util.Set<[Ljava.lang.Integer>>>"), extractor.getParamGenerics());
    }

    @Test
    public void testGenericsWithBounds() {
        // <T extends List<String>> void xxx(T arg0)
        String signature = "<T:Ljava/util/List<Ljava/lang/String;>;>(TT;)V";
        GenericTypeExtractor extractor = parseSignature(signature);
        Assert.assertEquals("", extractor.getReturnTypeGeneric());
        Assert.assertEquals(Collections.singletonList(""), extractor.getParamGenerics());
    }

    // 解析签名的辅助方法
    private GenericTypeExtractor parseSignature(String signature) {
        GenericTypeExtractor extractor = new GenericTypeExtractor(e -> e.replace("/", "."));
        SignatureReader reader = new SignatureReader(signature);
        reader.accept(extractor);
        return extractor;
    }
}

